<?php
namespace WDB;
/**
 * Base object for all WDB framework objects.
 * Features:
 * WDB magic properties = Auto getter/setter caller
 * If a property "xxx" is accessed, class inheriting BaseObject will access
 * it through getXxx() / setXxx($value) methods if defined.
 * If the property has only getXxx, any attempt to write to it will
 * throw an Exception\ReadOnlyProperty;
 * 
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 */
abstract class BaseObject
{
    
    /**
     * Magic getter. tries to invoke public getXxx() on non-existent xxx method.
     * 
     * @param string name
     * @return type
     */
    public function __get($name)
    {
        $m = 'get'.  ucfirst($name);
        if ($this->doIHavePublicMethod($m))
        {
            return $this->$m();
        }
        
        throw new Exception\NotExistingProperty($this, $name);
    }
    /**
     * Magic getter. tries to invoke public setXxx($value) on non-existent xxx method.
     * 
     * @param string name
     * @param string value 
     * @throws WDB\Exception\ReadOnlyProperty
     * @throws WDB\Exception\NotExistingProperty
     */
    public function __set($name, $value)
    {
        $m = 'set'.  ucfirst($name);
        if ($this->doIHavePublicMethod($m))
        {
            $this->$m($value);
        }
        elseif (method_exists($this, 'get'.  ucfirst($name)))
        {
            throw new Exception\ReadOnlyProperty($this, $name);
        }
        else
        {
            throw new Exception\NotExistingProperty($this, $name);
        }
    }
    
    /**
     * Returns true if this object has public method of specified name.
     *
     * @param string $methodName
     * @return bool
     */
    private function doIHavePublicMethod($methodName)
    {
        if (method_exists($this, $methodName))
        {
            $r = new \ReflectionMethod($this, $methodName);
            if ($r->isPublic())
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Property is considered to be set if it has an existing getter.
     *
     * @param string name
     * @return bool
     */
    public function __isset($name)
    {
        $m = 'get'.  ucfirst($name);
        return method_exists($this, $m);
    }
    
    /**
     * Unset cannot be called on magic getter/setter shortcuts.
     *
     * @param string name
     */
    public function __unset($name)
    {
        throw new \LogicException("cannot unset an object property.");
    }
    
    /**
     * Fills public or magic properties from associative array 
     * 
     * @param array $data
     * @return void
     * @throws WDB\Exception\BadArgument
     */
    public function _initPropertiesFromArray($data)
    {
        if ($data === NULL) return;
        if (!is_array($data)) throw new WDB\Exception\BadArgument("default object initializer must be an array");
        
        foreach ($data as $key=>$val)
        {
            if (property_exists($this, $key))
            {
                $r = new \ReflectionProperty($this, $key);
                if ($r->isPublic())
                {
                    $this->$key = $val;
                    continue;
                }
            }
            $this->__set($key, $val);
        }
    }
    
    /**
     * Determines if a property exists on this object. In contrast to __isset, this returns true even on write-only
     * magic properties (setXxx only)
     *
     * @param string $name
     * @return bool
     */
    
    public function _propertyExists($name)
    {
        return method_exists($this, 'get'.ucfirst($name)) || method_exists($this, 'set'.ucfirst($name));
    }
}
